DA Prototype
Volume Number: 2
Issue Number: 1
Column Tag: Pascal Procedures
Prototyping Desk Accessories 
By Alan Wootton, President, Top-Notch Productions, MacTutor
Contributing Editor
This month I will try to provide an interesting explanation of how to program
Desk Accessories. Rather than simply attempting to explain the purpose and function of
each of the three main procedures used by DA's (which has, after all, been done), we
will try an entirely different approach. We will start with a simple DA, and take for
granted the fact that it works. We will then type this DA's code into MacPascal and
attempt to get it to run. In the process we will learn a lot about DA's, and when it is all
done we will have a useful tool for prototyping Desk Accessories.
Perhaps at this point you are wondering "What do you mean, type it in and get it
to run?" We can't make the system call a MacPascal procedure the same way it calls
compiled 68000 procedures, so we will write a program that attempts to duplicate the
actions of the system and its relationship with DA's. [ A desk accessory simulator! -Ed.]
To start with let's take a look at the DA we will be using as a subject. Its' code is listed
below. Look at it briefly and then continue reading the text that follows.
The Simple DA We Will Be Using
procedure UpdateSelf (var device : deviceControlRec);
begin
with device do
begin
SetPort(dctlWindow);
BeginUpdate(dctlWindow);
MoveTo(10, 30);
DrawString('This is a test DA');
EndUpdate(dctlWindow);
end;{ of with }
end;{ of UpdateSelf }
procedure Open (var device : deviceControlRec;
var block :
ParamBlockRec);
var
R : Rect;
wP : windowPeek;
begin
with device do
begin
setrect(R, 128, 128, 256, 256);
dctlWindow :=
NewWindow(nil, R, 'Test DA', true, 0, nil, true,
0);
wP := pointer(ord(dctlWindow));
wP^. windowKind := dctlRefNum;
end;{ of with }
end;{ of open }
procedure Close (var device : deviceControlRec;
var block :
ParamBlockRec);
begin
with device do
begin
DisposeWindow(dctlWindow);
dctlWindow := nil;
end;{ of with }
end;{ of close }
procedure Event (var device : deviceControlRec;
var block : ParamBlockRec);
var
EventP : ^EventRecord;
begin
{ csParam holds a pointer to the event record }
{ copy it to EventP }
BlockMove(@block.csParam[0], @EventP, 4);
with EventP^ do{ eventRecord }
begin
case what of
1 : { mdown event }
SysBeep(1);
6 : { update event }
UpdateSelf(device);
otherwise
; { ignore all other events }
end;{ case of what }
end;{ with }
end;{ procedure event }
procedure Ctl (var device : deviceControlRec;
var block : ParamBlockRec);
var
poi : point;
begin
setport(device.dctlWindow);
with block do
begin
case csCode of
Event(device, block);
otherwise
;
{ other codes (accRun, accCursor, accMenu,
accCut, etc.) }
{ are not used by this DA }
end;{ case of code }
end;{ of with block }
end;{ of Ctl }
The first thing to notice about this DA is that it does practically nothing. The
Open procedure creates a window, the Ctl procedure only handles calls of type accEvent
(which are passed to the procedure event). The only events handled are Update, which
draws a string, and MouseDown which merely beeps. Finally, the Close procedure
Disposes the window. That's all it does. The next thing to notice is that there are some
data types referenced that MacPascal does not recognize. Scanning through the code we
encounter a DeviceControlRec, and then the ParamBlockRec. Further examination
reveals the type WindowPeek which is used once in the Open procedure. We will deal
with these three types in a moment. The final thing is the toolbox calls used by the DA.
We will declare equivalent procedures to these and use "inline" to make the actual calls.
It will be very straightforward with one minor twist.
Now let's get back to the type declarations. DeviceControlRec is not found
anywhere in Inside Macintosh! As it turns out, if you read the portion of the Desk
Manager on "writing your own Desk Accessories" it will mention the three driver
routines used and then refer you to the Device Manager for further details. In the
Device Manager chapter they mention that all driver routines recieve a pointer to the
calls parameter block in A0 ( there's the ParamBlockRec), and a pointer to the "Device
Control Entry" in A1. On page 21, titled "A Device Control Entry" we find the
description of what must be the DeviceControlRec. The description is not a Pascal type
declaration but we can easily convert it into one. The only fields accessed by the simple
DA are the dctlRefNum, and dctlWindow. DctlRefNum is the reference number of the
driver (related to the number of the DA), and dctlWindow is a place to put a pointer to
the window the DA uses. Once you become familiar with DA's the use of the other fields
is easily found.
The declaration of a ParamBlockRec is found in that same chapter. If we read the
DA carefully we see that csCode and csParam are the only parts referenced, so we won't
type all four of the variant parts, only what is needed. CsParam is declared as
array[0..0] of Byte which seems real stupid and dangerous to me so I changed it to
array[0..3] of Byte. In the DA csParam is used only as a pointer to an event record. It
would be convenient to change the definition of csParam to ^EventRecord, but let's stay
with the standard form. IM assumes that all DA's (and all drivers) are written in
assembly language. In assembly you can use csParam any way you wish. In Pascal the
type checking gets in the way, so I have adopted the habit of useing BlockMove to copy
things into an out of csParam.
To find the definition of WindowPeek we look, naturally, in the Window Manager
chapter of IM. To use this definition we must also provide declarations for a Handle,
and for a StringHandle. As I mentioned in previous articles, MacPascal allocates 2
bytes for boolean types while Lisa Pascal allocates 1 byte (1 is correct). We take this
into account in the declaration.
We are now ready to do the Type declarations, so here they are:
Type Declarations for the Sample DA
Lptr = ^longint;
ptr = ^integer;
Handle = ^ptr;
Byte = 0..255;
str255P = ^str255;
stringHandle = ^str255;
ParamBlockRec = record
qLink : Ptr;
qType : integer;
ioTrap : integer;
ioCmdAddr : ptr;
ioCompletion : ptr;
ioResult : integer;
ioNamePtr : ^str255;
ioVrefNum : integer;
{ Usually there are three variant parts here also. }
{ DA's use only csCode and csParam. }
csCode : integer;
csParam : array[0..3] of Byte;
end;
ParamBlkPtr = ^ParamBlockRec;
WindowPtr = GrafPtr;
WindowPeek = ^WindowRecord;
WindowRecord = record
port : GrafPort;
windowKind : Integer;
visible : Boolean;
goAwayFlag : Boolean;
strucRgn : RgnHandle;
contRgn : RgnHandle;
updateRgn : RgnHandle;
windowDefProc : Handle;
dataHandle : Handle;
titleHandle : StringHandle;
titleWidth : Integer;
ControlList : Handle;
nextWindow : WindowPeek;
windowPic : PicHandle;
refCon : LongInt;
end;
DeviceControlRec = record
dCltDriver : Handle;
DcltFlags : integer;
dctlQueue : integer;
DctlQHead : Lptr;
DctlQtail : Lptr;
DctlPosition : longint;
DctlStorage : Handle;
dCtlRefNum : integer;
dCtlCurTicks : longint;
dCtlWindow : GrafPtr;
dCtlDelay : integer;
dCtlEmask : integer;
dCtlMenu : integer;
end;
Now let's attack the issue of the toolbox calls. We will make procedure
declarations for the needed routines, and use inline in those declarations. This method
is clearer than using inline directly in the code. In the main procedure we will use
inline directly (for brevity). The NewWindow function is going to allocate a window
record on the heap, and MacPascal reacts very poorly to this (you get an out of memory
error). To alleviate this problem we pass a pointer to a window record to NewWindow.
We must remember later, when we are constructing the main procedure of our
program, to declare a variable named GlobalWindow as a WindowRecord. The ToolBox
interface is therefore:
ToolBox Interface routines
{--- Toolbox routines used by DA -----------------------}
{ NewWindow used by Open. }
{ Uses GlobalWindow variable for WindowRecord instead of }
{ letting the system allocate the memory automatically. }
function NewWindow (wStorage : ptr;
boundsRect : Rect;
title : str255;
visible : boolean;
procID : integer;
behind : windowPtr;
goAwayFlag : boolean;
refcon : longint) : WindowPtr;
begin
NewWindow := pointer(LinlineF($A913, @GlobalWindow,
@boundsRect, @title, visible,
procID,
behind, goAwayFlag, refcon));
end;
procedure BeginUpdate (TheWindow : WindowPtr);
begin
inlineP($A922, TheWindow);
end;
procedure EndUpdate (TheWindow : WindowPtr);
begin
inlineP($A923, TheWindow);
end;
procedure DisposeWindow (TheWindow : WindowPtr);
begin
inlineP($A914, TheWindow);
end;
{-------------------------------------------------------------------D